package org.gluedom.event;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import org.gluedom.data.Concept;
import org.gluedom.data.Identifier;
import org.gluedom.data.Immutable;

import com.google.common.collect.ImmutableList;

public class Event<T extends Immutable> {
	public static final Identifier EVENT_HEADERID = Identifier.id(URI
			.create("http://gluedom.org/ns/event#header"));

	private final boolean consumable;
	private final ImmutableList<Immutable> data;
	private final Concept header;
	private final Identifier type;

	private volatile int hash = 0;

	private Event(Builder<T> builder) {
		header = builder.header.build();
		checkArgument(header.getIdentifier().equals(EVENT_HEADERID),
				"event header not found");
		type = Identifier.unmarshall(header.getProperty("type"));
		String consumableString = header.getProperty("consumable");
		if (consumableString == null)
			consumable = false;
		else
			consumable = Boolean.parseBoolean(consumableString);
		List<Immutable> dataList = new ArrayList<Immutable>();
		dataList.add(header);
		dataList.addAll(builder.content);
		data = ImmutableList.copyOf(dataList);
	}

	public boolean isConsumable() {
		return consumable;
	}

	public ImmutableList<Immutable> getData() {
		return data;
	}

	public Iterable<T> getContent() {
		return new SectionIterable<T>(data, 1, data.size());
	}

	public Identifier getType() {
		return type;
	}

	public Concept getHeader() {
		return header;
	}

	private static class SectionIterable<V> implements Iterable<V> {
		private final int start;
		private final int end;
		private final List<?> data;

		public SectionIterable(List<?> data, int start, int end) {
			checkNotNull(data);
			this.data = data;
			this.start = start;
			this.end = end;
		}

		@Override
		public Iterator<V> iterator() {
			return new Iterator<V>() {
				private int current = start;

				@Override
				public boolean hasNext() {
					return current < end;
				}

				@Override
				public V next() {
					if (current >= end)
						throw new NoSuchElementException();
					@SuppressWarnings("unchecked")
					V result = (V) data.get(current++);
					return result;
				}

				@Override
				public void remove() {
					throw new UnsupportedOperationException();
				}
			};
		}
	}

	public static class Builder<T extends Immutable> {
		private List<T> content = new ArrayList<T>();
		private final Concept.Builder header;

		public Builder(ImmutableList<Immutable> data) {
			checkArgument(data.size() > 0, "data is empty");
			checkArgument(data.get(0) instanceof Concept,
					"event header not found");
			Concept headerConcept = (Concept) data.get(0);
			checkArgument(headerConcept.getIdentifier().equals(EVENT_HEADERID),
					"event header not found");
			this.header = new Concept.Builder(headerConcept);
			for (T element : new SectionIterable<T>(data, 1, data.size()))
				content.add(element);
		}

		public Builder(Event<T> event) {
			this.header = new Concept.Builder(event.getHeader());
			for (T element : event.getContent())
				content.add(element);
		}

		public Builder(Identifier type) {
			checkNotNull(type);
			this.header = new Concept.Builder(EVENT_HEADERID,
					Identifier.ROOT_OWNERID);
			this.header.property("type", Identifier.marshall(type));
		}

		public Concept.Builder header() {
			return header;
		}

		public Builder<T> content(Collection<? extends T> content) {
			checkNotNull(content);
			this.content.clear();
			this.content.addAll(content);
			return this;
		}

		public Builder<T> add(T element) {
			checkNotNull(element);
			this.content.add(element);
			return this;
		}

		public Builder<T> addAll(Collection<? extends T> elements) {
			checkNotNull(elements);
			this.content.addAll(elements);
			return this;
		}

		public Builder<T> consumable(boolean consumable) {
			header.property("consumable", Boolean.toString(consumable));
			return this;
		}

		public Builder<T> type(Identifier type) {
			header.property("type", Identifier.marshall(type));
			return this;
		}

		public Event<T> build() {
			return new Event<T>(this);
		}
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (!(obj instanceof Event<?>))
			return false;
		Event<?> other = (Event<?>) obj;
		if (other.hashCode() != hashCode())
			return false;
		if (!other.data.equals(data))
			return false;
		return true;
	}

	@Override
	public int hashCode() {
		if (hash != 0) {
			int result = 3535239;
			result = 31 * data.hashCode();
			if (result == 0)
				result = 1;
			hash = result;
		}
		return hash;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("Event (");
		sb.append(getType());
		sb.append("): ");
		sb.append(getHeader());
		sb.append(": ");
		sb.append(getContent());
		return sb.toString();
	}
}
